/*
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Topless JSF.
 *
 * The Initial Developer of the Original Code is
 * Marco Antonio Villalobos, Jr. (mvillalobos@kineteque.com).
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Marco Antonio Villalobos, Jr. (mvillalobos@kineteque.com).
 *
 * ***** END LICENSE BLOCK *****
 */

/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kineteque.jsf.topless.jdbc;

import org.springframework.jdbc.core.*;
import org.springframework.jdbc.core.namedparam.*;
import org.springframework.jdbc.support.JdbcUtils;

import org.springframework.dao.DataAccessException;

import javax.sql.DataSource;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.ResultSetMetaData;

/**
 * Provides a special use jdbc template.  This adds a paging feature that allows scrolling through the result list.
 */
public class ToplessJdbcTemplate extends NamedParameterJdbcTemplate{

    /**
     * Create a new NamedParameterJdbcTemplate for the given {@link javax.sql.DataSource}.
     * <p>Creates a classic Spring {@link org.springframework.jdbc.core.JdbcTemplate} and wraps it.
     *
     * @param dataSource the JDBC DataSource to access
     */
    public ToplessJdbcTemplate(DataSource dataSource) {
        super(dataSource);
    }

    /**
     * Create a new NamedParameterJdbcTemplate for the given classic
     * Spring {@link org.springframework.jdbc.core.JdbcTemplate}.
     *
     * @param classicJdbcTemplate the classic Spring JdbcTemplate to wrap
     */
    public ToplessJdbcTemplate(JdbcOperations classicJdbcTemplate) {
        super(classicJdbcTemplate);
    }

    /**
     * Retrieves the dataSource.
     *
     * @return The DataSource.
     */
    public DataSource getDataSource() {
        return ((JdbcTemplate)getJdbcOperations()).getDataSource();
    }

    /**
     * A result set extracter that scrolls to build a paginated list.
     */
    private static class PaginatedListExtractor implements ResultSetExtractor {

        /** The first row to retrieve. */
        private int first;
        /** The amount of rows to retrieve. */
        private int rows;
        /** A local instance of a column row mapper. */
        private RowMapper mapper;

        /**
         * Creates an instance of this paginated list extractor.
         *
         * @param first The first row to retrieve.
         * @param rows The amount of rows to retrieve.
         */
        public PaginatedListExtractor(int first, int rows) {
            this.first = first;
            this.rows = rows;
            this.mapper = new ColumnMapRowMapper();
        }

        /**
         * Extracts a paginated list from the given result set.
         *
         * @param rs The result set used to extract the data.
         * @return The paginated list.
         * @throws SQLException
         * @throws DataAccessException
         */
        @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"})
        public Object extractData(ResultSet rs) throws SQLException, DataAccessException {

            if (rs.getType()==ResultSet.TYPE_FORWARD_ONLY) {
                throw new RuntimeException("TYPE_FORWARD_ONLY ResultSet types not support.");
            }
                       
            PaginatedList<Map<String, Object>> paginatedList = new PaginatedList<Map<String, Object>>();
            List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
            List<String> columnNames = columnNames(rs);
            paginatedList.setColumnNames(columnNames);

            int matches = 0;
            if (rs.first()) {
                if (first >= 0) {
                    int i = 0;
                    rs.absolute(first+1);
                    do {
                        i++;
                        Map<String, Object> row = (Map<String, Object>) mapper.mapRow(rs, i);
                        list.add(row);
                        if (rs.isLast()) {
                            break;
                        }
                    }
                    while (rows == 0 && rs.next() || rs.next() && rows != 0 && i < rows);
                }
                rs.last();
                matches = rs.getRow();
            }

            paginatedList.setList(list);
            paginatedList.setRows(matches);

            return paginatedList;
        }

        /**
         * Retrieves the column names from the result set meta data.
         * @param rs The result set.
         * @return The column names.
         * @throws SQLException When there is an error.
         */
        protected List<String> columnNames(ResultSet rs) throws SQLException {
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            List<String> values = new ArrayList<String>(columnCount);
            for (int i = 0; i < columnCount; i++) {
                int columnIndex = i + 1;
                String name = JdbcUtils.lookupColumnName(rsmd, columnIndex);
                values.add(name);
            }
            return values;
        }
    }

    /**
	 * Build a PreparedStatementCreator based on the given SQL and named parameters.
	 * <p>Note: Not used for the <code>update</code> variant with generated key handling.
	 * @param sql SQL to execute
	 * @param paramSource container of arguments to bind
	 * @return the corresponding PreparedStatementCreator
	 */
	protected PreparedStatementCreator getPreparedStatementCreator(String sql, SqlParameterSource paramSource) {
		ParsedSql parsedSql = getParsedSql(sql);
		String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
		Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, null);
		int[] paramTypes = NamedParameterUtils.buildSqlTypeArray(parsedSql, paramSource);
		PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sqlToUse, paramTypes);
        pscf.setResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
		return pscf.newPreparedStatementCreator(params);
	}

    /**
     * Executes the given sql to retrieve a list of maps.
     *
     * @param sql  The sql with named parameters.
     *      Each parameter is marked with a :parameterName marker.
     *      This will be converted into a prepared statement.
     * @param parameters A map of parameter names and values that will be assigned to the prepared statement.
     * @param first The first row to retrieve.
     * @param rows The amount of rows to retrieve.
     * @return A paginated list.
     */
    @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"})
    public PaginatedList<Map<String, Object>> queryForList(String sql, Map<String, Object> parameters, Integer first, Integer rows) {
        SqlParameterSource src = new MapSqlParameterSource(parameters);
        PaginatedListExtractor ext = new PaginatedListExtractor(first==null?0:first, rows==null?0:rows);
        PaginatedList<Map<String, Object>> list = (PaginatedList<Map<String, Object>>) query(sql, src, ext);      
        return list;
    }

}